package com.fer.andenginemvc.api;

import java.util.Collection;

import org.andengine.engine.camera.Camera;
import org.andengine.entity.scene.Scene;
import org.andengine.extension.physics.box2d.PhysicsConnector;
import org.andengine.extension.physics.box2d.PhysicsConnectorManager;
import org.andengine.extension.physics.box2d.PhysicsWorld;
import org.andengine.ui.activity.SimpleBaseGameActivity;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.fer.andenginemvc.api.gameObjects.GameObject;

/**
 * A physics controller is a Controller + PhysicsWorld
 * @author mono
 *
 * @param <T>
 */
public abstract class PhysicsController<T extends SimpleBaseGameActivity> extends Controller<T> {

	protected PhysicsWorld physWorld;
	private boolean paused;
	
	public PhysicsController(Scene scene, T baseGame,
			Camera camera) {
		super(scene, baseGame, camera);
		initPhysicsEngine();
		paused = false;
	}

	/**
	 * inits all the stuff related to the physiscs engine
	 */
	private void initPhysicsEngine() {
		physWorld = new PhysicsWorld(new Vector2(0, 0), false);//FixedStepPhysicsWorld(30, new Vector2(0, 0), false, 8, 1);
		scene.registerUpdateHandler(physWorld);
		physWorld.setContactListener(new ContactListener() {
			
			@Override
			public void preSolve(Contact contact, Manifold oldManifold) {}
			@Override
			public void postSolve(Contact contact, ContactImpulse impulse) {}
			@Override
			public void endContact(Contact contact) {};
			
			@Override
			public void beginContact(Contact contact) {
				Body bodyA = contact.getFixtureA().getBody();
				Body bodyB = contact.getFixtureB().getBody();
				final GameObject<?> objA = GameObject.bodyMap.get(bodyA);
				final GameObject<?> objB = GameObject.bodyMap.get(bodyB);				
				baseGame.runOnUpdateThread(new Runnable() {
					
					@Override
					public void run() {
						onCollision(objA, objB);
					}
				});
				
			}
		});
	}
	
	/**
	 * Calls the GameObejct.onCollision method on each GameObject
	 * @param objA
	 * @param objB
	 */
	public void onCollision(GameObject<?> objA, GameObject<?> objB)
	{
		objA.onCollision(objB);
		objB.onCollision(objA);
	}
	
	/**
	 * Utility method for cheking collisions
	 * @param objA
	 * @param objB
	 * @param c1
	 * @param c2
	 * @return returns true if objA instanceof c1 && objB instanceof c2 or viceversa
	 */
	protected boolean matches(GameObject<?> objA, GameObject<?> objB, Class<?> c1, Class<?> c2)
	{
		if ((c1.isInstance(objA) && c2.isInstance(objB)) || (c1.isInstance(objB) && c2.isInstance(objA)))
		{
			return true;
		}
		return false;
	}
	
	/**
	 * Utility method for checking collisions
	 * @param objA
	 * @param objB
	 * @param c1
	 * @return returns true if either objA or objB is of type c1
	 */
	protected boolean matches(GameObject<?> objA, GameObject<?> objB, Class<?> c1)
	{
		if (c1.isInstance(objA) || c1.isInstance(objB))
		{
			return true;
		}
		return false;
	}
	/**
	 * Utility method for fast casting
	 * @param clazz
	 * @param objA
	 * @param objB
	 * @return returns one of objA or objB casted to type clazz if it is of that type.
	 */
	protected <O> O get(Class<O> clazz, GameObject<?> objA, GameObject<?> objB)
	{
		if (clazz.isInstance(objA))
		{
			return clazz.cast(objA);
		}
		else if (clazz.isInstance(objB))
		{
			return clazz.cast(objB);
		}
		else
		{
			throw new RuntimeException("One of objA or objB must be of type 'O'");
		}
	}
	
	public void pause()
	{
		
		paused = true;
		PhysicsConnectorManager connectors = physWorld.getPhysicsConnectorManager();
		for (PhysicsConnector physicsConnector : connectors) {
			physicsConnector.setUpdatePosition(false);
			physicsConnector.setUpdateRotation(false);
		}
		Collection<GameObject<?>> gos = GameObject.bodyMap.values();
		for (GameObject<?> go : gos) {
			go.body.setActive(false);
		}
	}
	
	public void resume()
	{
		paused = false;
		PhysicsConnectorManager connectors = physWorld.getPhysicsConnectorManager();
		for (PhysicsConnector physicsConnector : connectors) {
			physicsConnector.setUpdatePosition(true);
			physicsConnector.setUpdateRotation(true);
		}
		Collection<GameObject<?>> gos = GameObject.bodyMap.values();
		for (GameObject<?> go : gos) {
			go.body.setActive(true);
			
		}
	}
	
	public PhysicsWorld getPhysicsWorld()
	{
		return physWorld;
	}
	
	public boolean isPaused() {
		return paused;
	}

}
